//
// run random system calls in parallel forever.
//

#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
#include "kernel/fcntl.h"
#include "kernel/syscall.h"
#include "kernel/memlayout.h"
#include "kernel/riscv.h"

// from FreeBSD.
int do_rand(unsigned long *ctx)
{
    /*
     * Compute x = (7^5 * x) mod (2^31 - 1)
     * without overflowing 31 bits:
     *      (2^31 - 1) = 127773 * (7^5) + 2836
     * From "Random number generators: good ones are hard to find",
     * Park and Miller, Communications of the ACM, vol. 31, no. 10,
     * October 1988, p. 1195.
     */
    long hi, lo, x;

    /* Transform to [1, 0x7ffffffe] range. */
    x = (*ctx % 0x7ffffffe) + 1;
    hi = x / 127773;
    lo = x % 127773;
    x = 16807 * lo - 2836 * hi;
    if (x < 0)
        x += 0x7fffffff;
    /* Transform to [0, 0x7ffffffd] range. */
    x--;
    *ctx = x;
    return (x);
}

unsigned long rand_next = 1;

int rand(void)
{
    return (do_rand(&rand_next));
}

void go(int which_child)
{
    int fd = -1;
    static char buf[999];
    char *break0 = sbrk(0);
    uint64 iters = 0;

    mkdir("grindir");
    if (chdir("grindir") != 0)
    {
        printf("grind: chdir grindir failed\n");
        exit(1);
    }
    chdir("/");

    while (1)
    {
        iters++;
        if ((iters % 500) == 0)
            write(1, which_child ? "B" : "A", 1);
        int what = rand() % 23;
        if (what == 1)
        {
            close(open("grindir/../a", O_CREATE | O_RDWR));
        }
        else if (what == 2)
        {
            close(open("grindir/../grindir/../b", O_CREATE | O_RDWR));
        }
        else if (what == 3)
        {
            unlink("grindir/../a");
        }
        else if (what == 4)
        {
            if (chdir("grindir") != 0)
            {
                printf("grind: chdir grindir failed\n");
                exit(1);
            }
            unlink("../b");
            chdir("/");
        }
        else if (what == 5)
        {
            close(fd);
            fd = open("/grindir/../a", O_CREATE | O_RDWR);
        }
        else if (what == 6)
        {
            close(fd);
            fd = open("/./grindir/./../b", O_CREATE | O_RDWR);
        }
        else if (what == 7)
        {
            write(fd, buf, sizeof(buf));
        }
        else if (what == 8)
        {
            read(fd, buf, sizeof(buf));
        }
        else if (what == 9)
        {
            mkdir("grindir/../a");
            close(open("a/../a/./a", O_CREATE | O_RDWR));
            unlink("a/a");
        }
        else if (what == 10)
        {
            mkdir("/../b");
            close(open("grindir/../b/b", O_CREATE | O_RDWR));
            unlink("b/b");
        }
        else if (what == 11)
        {
            unlink("b");
            link("../grindir/./../a", "../b");
        }
        else if (what == 12)
        {
            unlink("../grindir/../a");
            link(".././b", "/grindir/../a");
        }
        else if (what == 13)
        {
            int pid = fork();
            if (pid == 0)
            {
                exit(0);
            }
            else if (pid < 0)
            {
                printf("grind: fork failed\n");
                exit(1);
            }
            wait(0);
        }
        else if (what == 14)
        {
            int pid = fork();
            if (pid == 0)
            {
                fork();
                fork();
                exit(0);
            }
            else if (pid < 0)
            {
                printf("grind: fork failed\n");
                exit(1);
            }
            wait(0);
        }
        else if (what == 15)
        {
            sbrk(6011);
        }
        else if (what == 16)
        {
            if (sbrk(0) > break0)
                sbrk(-(sbrk(0) - break0));
        }
        else if (what == 17)
        {
            int pid = fork();
            if (pid == 0)
            {
                close(open("a", O_CREATE | O_RDWR));
                exit(0);
            }
            else if (pid < 0)
            {
                printf("grind: fork failed\n");
                exit(1);
            }
            if (chdir("../grindir/..") != 0)
            {
                printf("grind: chdir failed\n");
                exit(1);
            }
            kill(pid);
            wait(0);
        }
        else if (what == 18)
        {
            int pid = fork();
            if (pid == 0)
            {
                kill(getpid());
                exit(0);
            }
            else if (pid < 0)
            {
                printf("grind: fork failed\n");
                exit(1);
            }
            wait(0);
        }
        else if (what == 19)
        {
            int fds[2];
            if (pipe(fds) < 0)
            {
                printf("grind: pipe failed\n");
                exit(1);
            }
            int pid = fork();
            if (pid == 0)
            {
                fork();
                fork();
                if (write(fds[1], "x", 1) != 1)
                    printf("grind: pipe write failed\n");
                char c;
                if (read(fds[0], &c, 1) != 1)
                    printf("grind: pipe read failed\n");
                exit(0);
            }
            else if (pid < 0)
            {
                printf("grind: fork failed\n");
                exit(1);
            }
            close(fds[0]);
            close(fds[1]);
            wait(0);
        }
        else if (what == 20)
        {
            int pid = fork();
            if (pid == 0)
            {
                unlink("a");
                mkdir("a");
                chdir("a");
                unlink("../a");
                fd = open("x", O_CREATE | O_RDWR);
                unlink("x");
                exit(0);
            }
            else if (pid < 0)
            {
                printf("grind: fork failed\n");
                exit(1);
            }
            wait(0);
        }
        else if (what == 21)
        {
            unlink("c");
            // should always succeed. check that there are free i-nodes,
            // file descriptors, blocks.
            int fd1 = open("c", O_CREATE | O_RDWR);
            if (fd1 < 0)
            {
                printf("grind: create c failed\n");
                exit(1);
            }
            if (write(fd1, "x", 1) != 1)
            {
                printf("grind: write c failed\n");
                exit(1);
            }
            struct stat st;
            if (fstat(fd1, &st) != 0)
            {
                printf("grind: fstat failed\n");
                exit(1);
            }
            if (st.size != 1)
            {
                printf("grind: fstat reports wrong size %d\n", (int)st.size);
                exit(1);
            }
            if (st.ino > 200)
            {
                printf("grind: fstat reports crazy i-number %d\n", st.ino);
                exit(1);
            }
            close(fd1);
            unlink("c");
        }
        else if (what == 22)
        {
            // echo hi | cat
            int aa[2], bb[2];
            if (pipe(aa) < 0)
            {
                fprintf(2, "grind: pipe failed\n");
                exit(1);
            }
            if (pipe(bb) < 0)
            {
                fprintf(2, "grind: pipe failed\n");
                exit(1);
            }
            int pid1 = fork();
            if (pid1 == 0)
            {
                close(bb[0]);
                close(bb[1]);
                close(aa[0]);
                close(1);
                if (dup(aa[1]) != 1)
                {
                    fprintf(2, "grind: dup failed\n");
                    exit(1);
                }
                close(aa[1]);
                char *args[3] = {"echo", "hi", 0};
                exec("grindir/../echo", args);
                fprintf(2, "grind: echo: not found\n");
                exit(2);
            }
            else if (pid1 < 0)
            {
                fprintf(2, "grind: fork failed\n");
                exit(3);
            }
            int pid2 = fork();
            if (pid2 == 0)
            {
                close(aa[1]);
                close(bb[0]);
                close(0);
                if (dup(aa[0]) != 0)
                {
                    fprintf(2, "grind: dup failed\n");
                    exit(4);
                }
                close(aa[0]);
                close(1);
                if (dup(bb[1]) != 1)
                {
                    fprintf(2, "grind: dup failed\n");
                    exit(5);
                }
                close(bb[1]);
                char *args[2] = {"cat", 0};
                exec("/cat", args);
                fprintf(2, "grind: cat: not found\n");
                exit(6);
            }
            else if (pid2 < 0)
            {
                fprintf(2, "grind: fork failed\n");
                exit(7);
            }
            close(aa[0]);
            close(aa[1]);
            close(bb[1]);
            char buf[4] = {0, 0, 0, 0};
            read(bb[0], buf + 0, 1);
            read(bb[0], buf + 1, 1);
            read(bb[0], buf + 2, 1);
            close(bb[0]);
            int st1, st2;
            wait(&st1);
            wait(&st2);
            if (st1 != 0 || st2 != 0 || strcmp(buf, "hi\n") != 0)
            {
                printf("grind: exec pipeline failed %d %d \"%s\"\n", st1, st2, buf);
                exit(1);
            }
        }
    }
}

void iter()
{
    unlink("a");
    unlink("b");

    int pid1 = fork();
    if (pid1 < 0)
    {
        printf("grind: fork failed\n");
        exit(1);
    }
    if (pid1 == 0)
    {
        rand_next ^= 31;
        go(0);
        exit(0);
    }

    int pid2 = fork();
    if (pid2 < 0)
    {
        printf("grind: fork failed\n");
        exit(1);
    }
    if (pid2 == 0)
    {
        rand_next ^= 7177;
        go(1);
        exit(0);
    }

    int st1 = -1;
    wait(&st1);
    if (st1 != 0)
    {
        kill(pid1);
        kill(pid2);
    }
    int st2 = -1;
    wait(&st2);

    exit(0);
}

int main()
{
    while (1)
    {
        int pid = fork();
        if (pid == 0)
        {
            iter();
            exit(0);
        }
        if (pid > 0)
        {
            wait(0);
        }
        sleep(20);
        rand_next += 1;
    }
}
